<template>
  <div class="upload-folder">
    <el-dialog title="错误信息" append-to-body :visible.sync="errorDialogShow" width="80%">
      <!-- <el-tree
        class="error-tree"
        :data="precheckList"
        node-key="id"
        default-expand-all
        :expand-on-click-node="false"
        :props="{
          children: 'list',
          label: 'value'
        }"
      >
        <span class="custom-tree-node" slot-scope="{ node, data }">
          <span>{{ node.label }}</span>
          <span class="el-icon-success" v-if="data.ok" :style="{'color': 'green'}"></span>
          <template v-else>
            <span class="el-icon-error" :style="{'color': 'red'}"></span>
            <span :style="{'color': 'red'}">{{ data.msg }}</span>
          </template>
        </span>
      </el-tree> -->
      <el-table
        :data="precheckList"
        style="width: 100%; margin-bottom: 20px"
        height="600"
        row-key="id"
        border
        default-expand-all
        :tree-props="{ children: 'list' }"
      >
        <el-table-column prop="template" label="文件名"></el-table-column>
        <el-table-column prop="ok" label="状态" width="100" align="center">
          <template slot-scope="scope">
            <el-tag v-if="scope.row.ok" type="success">正确</el-tag>
            <el-tag v-else type="danger">错误</el-tag>
          </template>
        </el-table-column>
        <el-table-column prop="msg" label="错误信息"> </el-table-column>
      </el-table>
      <span slot="footer" class="dialog-footer">
        <el-button @click="errorDialogShow = false">取 消</el-button>
        <el-button type="primary" @click="errorDialogShow = false">确 定</el-button>
      </span>
    </el-dialog>
    <div class="upload-box" v-if="!disabled">
      <template v-if="taskId">
        <FolderInput :upload-folder="true" @change="handleChange" />
        <el-button class="btn-upload" type="primary" @click="clickUpload">
          <i class="el-icon-upload el-icon--right"></i>
          上传
        </el-button>
      </template>
      <el-button v-else @click="handleClick" :size="btnSize">选择文件夹</el-button>
      <span class="file-total">已选择{{ upladFileTotal }}个文件</span>
      <el-button v-if="haveErrorMessage" class="btn-error" type="danger" @click="checkError" :size="btnSize"> 查看错误信息 </el-button>
    </div>

    <div class="record-box" v-if="hasRecord">
      <el-tag class="file-path">文件路径：{{ recordData.root_path }}</el-tag>
      <el-tag type="success" class="status" v-if="recordData.status === 1">
        已上传： {{ recordData.file_count }} 个文件
      </el-tag>

      <!-- TODO 加一个错误的状态 -->
      <!-- <el-tag type="danger" class="status" v-if="recordData.status === 1"
        >错误信息： {{ recordData.file_count }} 个文件</el-tag
      > -->
    </div>

    <template v-if="uploadStatus === 1">
      <div class="tota-progress">
        <div class="title">上传进度：</div>
        <el-progress class="progress" :text-inside="true" :stroke-width="26" :percentage="totalPercentage" />
      </div>
      <div class="chunk-progress">
        <div class="title">正在上传：</div>
        <div v-if="upladFileName" class="ellipsis name">{{ upladFileName }}</div>
        <el-progress type="dashboard" :percentage="chunkPercentage" :color="colors" />
      </div>
    </template>
  </div>
</template>

<script>
import FolderInput from './FolderInput.vue'
// import SparkMD5 from "spark-md5";
import { md5 } from 'hash-wasm' // hash-wasm 优化md5速度
import { getToken } from '@/utils/auth'
import axios from 'axios'
// import { concurRequest } from './utils'

import { uploadStart, uploadVerify, uploadFinish, getUploadStatus, uploadPrecheck } from '@/api/form/upload'

const baseURL = process.env.VUE_APP_BASE_API

let upliadFiles = null

export default {
  name: 'UploadFolder',
  components: {
    FolderInput
  },
  props: {
    taskId: {
      required: true,
      type: String
    },
    chunkSize: {
      type: Number,
      default: 1024 * 1024 * 10 // 10MB
    },
    // showUpload: {
    //   type: Boolean,
    //   default: true
    // },
    chooseAlert: {
      required: false,
      type: String,
      default: '请保存后再上传文件'
    },
    btnSize: {
      type: String,
      default: 'medium'
    },
    disabled: {
      type: Boolean,
      default: false
    }
  },
  data() {
    return {
      loading: false,
      upladFileName: '',
      upladFileIndex: 0,
      upladFileTotal: 0,
      chunkIndex: 0,
      chunkTotal: 0,
      colors: [
        { color: '#f56c6c', percentage: 20 },
        { color: '#e6a23c', percentage: 40 },
        { color: '#5cb87a', percentage: 60 },
        { color: '#1989fa', percentage: 80 },
        { color: '#6f7ad3', percentage: 100 }
      ],
      stop: false,
      uploadStatus: 0,
      statusMap: {
        0: '未开始上传',
        1: '上传中',
        2: '上传完成'
      },
      hasRecord: false,
      recordData: {
        root_path: '',
        status: 0,
        file_count: 0
      },
      haveErrorMessage: false,
      precheckList: [],
      errorDialogShow: false
    }
  },
  computed: {
    totalPercentage() {
      return this.upladFileIndex > 0 ? Math.ceil((this.upladFileIndex / this.upladFileTotal) * 100) : 0
    },
    chunkPercentage() {
      return this.chunkIndex > 0 ? Math.ceil((this.chunkIndex / this.chunkTotal) * 100) : 0
    }
  },
  watch: {
    uploadStatus: {
      immediate: true,
      handler(newVal) {
        if (newVal === 1) {
          this.loading = true
        } else {
          this.loading = false
        }
      }
    }
  },
  created() {
    this.resetData()
    this.getStatus()
  },
  beforeDestroy() {
    console.log('upload-folder-beforeDestroy')
    this.stop = true
  },
  methods: {
    resetData() {
      upliadFiles = null
    },
    checkIsDone() {
      if (this.totalPercentage >= 100) {
        this.uploadStatus = 2
      } else {
        this.uploadStatus = 1
      }
    },
    // 1）提供两个保存按钮，一个保存不关闭当前页面，另外一个是保存关闭当前页面。
    // 2）客户未选择文件夹上传的按钮时，直接保存即可
    // 3）客户如果选择文件夹上传按钮时，先弹出对话框让客户保存

    handleClick() {
      this.$message.error(this.chooseAlert)
    },

    handleChange(e) {
      upliadFiles = e
      if (upliadFiles && upliadFiles.files && upliadFiles.files.length > 0) {
        this.upladFileTotal = Array.from(upliadFiles.files).length
      }
    },
    clickUpload() {
      if (!upliadFiles) {
        this.$message.error('请选择文件')
        return
      }
      this.handleUpload()
    },
    checkError() {
      console.log('checkError')
      this.errorDialogShow = true
    },
    async handleUpload() {
      if (this.loading) return

      let files = null
      if (this.upladFileTotal > 0) {
        files = Array.from(upliadFiles.files)
      } else {
        this.$message.error('请选择文件')
        return
      }

      // TODO 提交前校验文件列表
      let fileArry = files.map(item => {
        return item.webkitRelativePath
      })
      console.log('fileArry', fileArry)

      try {
        const precheckRes = await uploadPrecheck({
          taskId: this.taskId,
          list: fileArry
        })

        console.log('precheckRes', precheckRes)
        this.precheckList = precheckRes.data

        if (!precheckRes || !precheckRes.ok) {
          this.haveErrorMessage = true
          this.$message.error('格式错误，请修改后上传')
          return
        } else {
          this.haveErrorMessage = false
        }
      } catch (error) {
        console.log('precheckRes-error', error)
        this.$message.error(error)
        return
      }

      // ----------------------------------------------------------------------------------------start
      // 第1步，通知服务器针对dataId开始上传，告诉这次批量上传的目录和文件数量
      const path0 = files[0].webkitRelativePath
      const index = path0.indexOf('/')
      const curDir = path0.substring(0, index)

      try {
        const startRes = await uploadStart({
          taskId: this.taskId,
          fileDir: curDir,
          fileCount: files.length
        })

        console.log('startRes', startRes)
      } catch (error) {
        console.log('start-error', error)
        return
      }

      // ----------------------------------------------------------------------------------------end
      this.upladFileIndex = 0

      while (this.upladFileIndex < this.upladFileTotal) {
        if (this.stop) return

        this.statusName = '创建文件分片'
        this.upladFileName = files[this.upladFileIndex].name

        await this.createChunks({
          file: files[this.upladFileIndex],
          name: files[this.upladFileIndex].webkitRelativePath
        })

        this.upladFileIndex += 1

        this.checkIsDone()
      }
      // ----------------------------------------------------------------------------------------start
      // 最后1步，通知服务器针对dataId 上传结束，告诉这次批量上传的目录和文件数量
      let finishRes = await uploadFinish({
        taskId: this.taskId,
        fileDir: curDir,
        fileCount: files.length
      })

      if (finishRes.code === 200) {
        this.$message.success('上传成功')
      }
      this.getStatus()

      // ----------------------------------------------------------------------------------------end
    },
    async createChunks(data) {
      let start = 0
      this.chunkIndex = 0

      // 整个文件的fileHash，不去计算整个文件的md5，文件名+文件大小
      const fileHash = encodeURIComponent(data.name) + data.file.size
      const fileName = data.name

      this.chunkTotal = data.file.size > this.chunkSize ? Math.ceil(data.file.size / this.chunkSize) : 1

      // 校验文件、文件分片是否存在
      const verifyRes = await uploadVerify({
        taskId: this.taskId,
        fileHash,
        fileName
      })

      const { existFile, existChunks } = verifyRes.data

      console.log('verifyRes.data', verifyRes.data)
      if (existFile) {
        console.log('此文件已存在')
        return
      }

      while (start < data.file.size) {
        if (this.stop) return

        const chunk = data.file.slice(start, start + this.chunkSize)

        const curHash = {
          taskId: this.taskId,
          fileHash,
          name: fileName,
          chunk: chunk,
          chunkHash: (await this.createHash(chunk)) + '-' + this.chunkIndex
        }

        // 分片是否存在
        if (!existChunks.includes(curHash.chunkHash)) {
          // 请求接口上传分片
          const uploadRes = await this.uploadChunks(curHash)
          console.log('uploadRes', uploadRes)
          if (uploadRes && uploadRes.data && uploadRes.data.code === 200) {
            console.log('上传分片成功')
          } else {
            this.$message.error('上传分片失败')
            this.stop = true
            this.uploadStatus = 0
            return
          }
        }
        this.chunkIndex += 1
        start += this.chunkSize
      }

      await this.mergeRequest({
        taskId: this.taskId,
        fileHash,
        fileName
      })
    },
    createHash(chunk) {
      return new Promise(resolve => {
        const fileReader = new FileReader()
        fileReader.readAsArrayBuffer(chunk)
        fileReader.onload = async function (e) {
          // const spark = new SparkMD5.ArrayBuffer();
          // spark.append(e.target.result);
          // resolve(spark.end());
          const hash = await md5(new Uint8Array(e.target.result))
          resolve(hash)
        }
      })
    },
    // 上传文件分片
    async uploadChunks(chunk) {
      const formData = new FormData()
      formData.append('taskId', chunk.taskId)
      formData.append('fileName', chunk.name)
      formData.append('fileHash', chunk.fileHash)
      formData.append('chunkHash', chunk.chunkHash)
      formData.append('chunk', chunk.chunk)
      return new Promise((resolve, reject) => {
        axios
          .post(baseURL + '/web/upload/chunk', formData, {
            headers: { Authorization: 'Bearer ' + getToken() }
          })
          .then(res => {
            console.log('Promise-res', res)

            resolve(res)
          })
          .catch(e => {
            reject(e)
          })
      })
    },

    // 合并分片请求
    async mergeRequest(data) {
      return new Promise((resolve, reject) => {
        axios
          .post(baseURL + '/web/upload/merge', data, {
            headers: { Authorization: 'Bearer ' + getToken() }
          })
          .then(res => {
            resolve(res)
          })
          .catch(e => {
            reject(e)
          })
      })
    },
    async getStatus() {
      if (!this.taskId) {
        console.log('无taskId')
        return
      }
      try {
        const statusRes = await getUploadStatus({
          taskId: this.taskId
        })

        console.log('statusRes', statusRes)
        if (statusRes?.code === 200 && statusRes.data) {
          this.hasRecord = true

          this.recordData = statusRes.data
        } else {
          this.hasRecord = false
        }
      } catch (error) {
        console.log('statusRes', error)
        return
      }
    }
  }
}
</script>
<style lang="scss" scoped>
::v-deep .error-tree {
  .custom-tree-node {
    // display: flex;

    width: 100%;
  }
}
.upload-folder {
  font-size: 14px;

  .title {
    min-width: 100px;
  }

  .record-box {
    .status {
      margin-right: 20px;
    }
    .file-path {
      margin-right: 20px;
    }
  }

  .btn-upload {
    margin-left: 20px;
  }

  .upload-box {
    display: flex;
    margin-bottom: 20px;
    align-items: center;

    .file-total {
      margin-left: 20px;
      min-width: 120px;
      display: inline-block;
    }

    .status {
      margin-left: 50px;
    }

    .upload-btn {
      margin-left: 50px;
    }
  }

  .tota-progress {
    margin-bottom: 20px;
    display: flex;
    align-items: center;

    .progress {
      width: 100%;
    }
  }

  .chunk-progress {
    margin-bottom: 20px;

    display: flex;
    align-items: center;

    ::v-deep .el-progress {
      width: 32px;
      height: 32px;

      .el-progress-circle {
        width: 32px !important;
        height: 32px !important;
      }

      .el-progress__text {
        font-size: 12px !important;
      }
    }

    .name {
      display: inline-block;
      max-width: 300px;
      margin-right: 20px;
    }
  }
}
</style>
